package com.bruce.tool.validate.hibernate.resolver;

import com.bruce.tool.validate.hibernate.domain.BaseResult;
import org.apache.commons.lang3.StringUtils;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.jdbc.UncategorizedSQLException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.sql.SQLIntegrityConstraintViolationException;

/**
 * 功能 :
 * 校验异常处理器
 * @author : Bruce(刘正航) 21:25 2019-04-14
 */
public class ValidExceptionResolver extends AbstractHandlerExceptionResolver {

    @Override
    protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        ModelAndView mv = new ModelAndView();
        MappingJackson2JsonView view = new MappingJackson2JsonView();
        mv.setView(view);
        BaseResult result = exceptionHandler(ex);
        if( null == result ){ return null; }
        mv.addObject(result);
        return mv;
    }

    public BaseResult exceptionHandler(Throwable ex) {
        BaseResult result = new BaseResult();
        if( ex instanceof DuplicateKeyException){
            handleDuplicateKeyInfo(ex,result);
        }else if( ex instanceof MethodArgumentTypeMismatchException){
            handleArumentTypeExceptionInfo(ex,result);
        } else if( ex instanceof HttpRequestMethodNotSupportedException){
            handleRequestMethodExceptionInfo(ex,result);
        } else if( ex instanceof UncategorizedSQLException){
            handleSQLExceptionInfo(ex, result);
        } else if( ex instanceof MissingServletRequestParameterException){//必传参数异常
            handleRequiredBindException(ex,result);
        } else if( ex instanceof MissingPathVariableException) { //url路径中的参数校验异常
            handleRequiredPathException(ex, result);
        } else if( ex instanceof MethodArgumentNotValidException){
            handleMethodBindException(ex, result);
        } else if( ex instanceof BindException){//hibernate-validation异常
            handleBindException(ex,result);
        } else {
            result = null;
        }
        return result;
    }

    /**唯一主键冲突**/
    private static void handleDuplicateKeyInfo(Throwable ex, BaseResult result) {
        DuplicateKeyException unique = (DuplicateKeyException)ex;
        Throwable throwable = unique.getCause();
        if( throwable instanceof SQLIntegrityConstraintViolationException ){
            handleConstraintViolationInfo(throwable,result);
        }
    }

    /**处理唯一索引冲突的异常**/
    private static void handleConstraintViolationInfo(Throwable ex, BaseResult result) {
        SQLIntegrityConstraintViolationException unique = (SQLIntegrityConstraintViolationException)ex;
        String message = unique.getMessage();
        if( message.contains("Duplicate") ){
            message = "违反数据库唯一索引约束";
        }
        result.setBusinessErrorKey("DuplicateDataError");
        result.setCode(1);
        result.setMessage(message);
    }

    /**参数类型错误**/
    private static void handleArumentTypeExceptionInfo(Throwable ex, BaseResult result) {
        MethodArgumentTypeMismatchException argumentTypeException = (MethodArgumentTypeMismatchException)ex;
        result.setCode(1);
        Class<?> requiredType = argumentTypeException.getRequiredType();
        String message = "参数类型错误:"+argumentTypeException.getName()
                +"(" + ((null != requiredType)?requiredType.getName():"null")
                + "),实际的传入值为:"+argumentTypeException.getValue();
        result.setBusinessErrorKey("paramTypeError");
        result.setMessage(message);
    }

    /**请求方法不支持**/
    private static void handleRequestMethodExceptionInfo(Throwable ex, BaseResult result) {
        HttpRequestMethodNotSupportedException methodException = (HttpRequestMethodNotSupportedException)ex;
        result.setCode(1);
        result.setBusinessErrorKey("requestMethodError");
        String message = "该方法不支持"+methodException.getMethod()+"请求";
        result.setMessage(message);
    }

    /**特殊SQL异常，返回值处理**/
    private static void handleSQLExceptionInfo(Throwable ex, BaseResult mav) {
        // 针对特殊符号，做特殊的提示信息:Caused by: java.sql.SQLException: Incorrect string value: '\xF0\x9F\x98\x91' for column 'remark' at row 1
        UncategorizedSQLException e = (UncategorizedSQLException)ex;
        String msg = e.getMessage();
        if( !StringUtils.isEmpty(msg) && msg.contains("Incorrect string value: '\\x") ){
            mav.setCode(1);
            mav.setBusinessErrorKey("mojiNotSupportError");
            mav.setMessage("暂不支持表情符号输入，请删除表情再试");
        }
    }

    /**参数校验异常捕获**/
    private static void handleRequiredBindException(Throwable ex, BaseResult mav) {
        MissingServletRequestParameterException bindException = (MissingServletRequestParameterException)ex;
        mav.setCode(1);
        mav.setBusinessErrorKey("missParamError");
        String message = "缺少必传参数:" + bindException.getParameterName();
        mav.setMessage(message);
    }

    private static void handleRequiredPathException(Throwable ex, BaseResult mav) {
        MissingPathVariableException pathVariableException = (MissingPathVariableException) ex;
        mav.setCode(1);
        mav.setBusinessErrorKey("missPathParamError");
        String message = "缺少必传参数:" + pathVariableException.getVariableName();
        mav.setMessage(message);
    }

    private static void handleMethodBindException(Throwable ex, BaseResult mav) {
        MethodArgumentNotValidException bindException = (MethodArgumentNotValidException)ex;
        handleBindError(mav, bindException.getBindingResult());
    }

    /**参数校验异常捕获**/
    private static void handleBindException(Throwable ex, BaseResult mav) {
        BindException bindException = (BindException)ex;
        handleBindError(mav, bindException.getBindingResult());
    }

    private static void handleBindError(BaseResult mav, BindingResult br) {
        if (br.hasFieldErrors()) {
            FieldError fieldError = br.getFieldError();
            if( null != fieldError && org.apache.commons.lang3.StringUtils.isNotBlank(fieldError.getDefaultMessage()) ){
                String error = fieldError.getDefaultMessage();
                mav.setBusinessErrorKey("paramValidError");
                mav.setMessage(fieldError.getField()+error);
                mav.setCode(1);
            }
        }
    }
}
